package com.flejay.dreamrecruiter.service.impl;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import com.flejay.dreamrecruiter.enums.OverSizedCaseEnum.WhatToDo;
import com.flejay.dreamrecruiter.model.AbstractBlock;
import com.flejay.dreamrecruiter.model.AbstractTag;
import com.flejay.dreamrecruiter.model.Block;
import com.flejay.dreamrecruiter.model.CurriculumVitae;
import com.flejay.dreamrecruiter.model.SubBlock;
import com.flejay.dreamrecruiter.model.Tag;
import com.flejay.dreamrecruiter.pojo.Bag;
import com.flejay.dreamrecruiter.service.IExtractorService;
import com.flejay.dreamrecruiter.service.ILauncherService;
import com.flejay.dreamrecruiter.service.IParsingEngineService;
import com.flejay.dreamrecruiter.service.ITransformerService;
import com.flejay.dreamrecruiter.tools.BlockSizerTool;
import com.flejay.dreamrecruiter.tools.BlockTool;
import com.flejay.dreamrecruiter.tools.DateTool;
import com.flejay.dreamrecruiter.tools.ParamControllerTool;
import com.flejay.dreamrecruiter.tools.IConstantsUtil;
import com.flejay.dreamrecruiter.tools.TagTool;

@Singleton
@Named("launcherService")
public class LauncherService implements ILauncherService{

	@Inject
	@Named("extractorFileService")
	private IExtractorService extractor;
	
	@Inject
	@Named("transformerService")
	private ITransformerService transformer;
	
	@Inject
	@Named("parsingEngineService")
	private IParsingEngineService parsingEngineService;
	
	@Override
	public List<CurriculumVitae> launchCVAnalysis(String mode, String language,List<CurriculumVitae> cvs){
		ParamControllerTool.controlString(mode, "mode");
		ParamControllerTool.controlString(language, "language");
		ParamControllerTool.controlList(cvs,"List<CurriculumVitae>");
		List<Tag> tags = extractor.loadConfiguration(mode, language);
		extractBlocks(cvs, tags);
		calculateOversize(cvs);
		reAssociationOfOversizedBlocks(cvs, tags);
		extractSubTags(cvs);
//		associateDate(cvs);
		associateDatePileApproach(cvs);
		return cvs;
	}

	/**
	 * On empile les dates par ordre d'apparition puis on les associe avec les sous blocs par ordre d'apparition
	 * @param res
	 */
	private void associateDatePileApproach(List<CurriculumVitae> res){
		for(CurriculumVitae cv : res){
			for(Block block : cv.getBlockList()){
				List<SubBlock> subBlocksToRemove = extractDatePileApproach(block);
				block.getSubBlockList().removeAll(subBlocksToRemove);			}
		}
	}
	
	/**
	 * On empile les dates par ordre d'apparition puis on les associe avec les sous blocs par ordre d'apparition
	 * On retourne la liste des dates associés pour pouvoir les supprimer du contenu
	 * 
	 * @param block
	 * @return
	 */
	private List<SubBlock> extractDatePileApproach(Block block) {
		List<SubBlock> datePile = new ArrayList<SubBlock>();
		List<SubBlock> nonDatePile = new ArrayList<SubBlock>();
		List<SubBlock> associatedDates = new ArrayList<SubBlock>();
		for(SubBlock subBlock : block.getSubBlockList()){
			if("date".equalsIgnoreCase(subBlock.getAssociatedTag().getLabel())){
				datePile.add(subBlock);
			} else {
				nonDatePile.add(subBlock);
			}
		}
		int i = 0;
		for(SubBlock dateBlock : datePile){
			if (i < nonDatePile.size()){
				nonDatePile.get(i).setStartDate(dateBlock.getStringContent());
				associatedDates.add(dateBlock);
			} else {
				// inutile d'aller plus loin
				break;
			}
			i++;
		}
		return associatedDates;
	}

	/**
	 * Associate Date to sub blocks
	 * 
	 * @param res
	 */
	private void associateDate(List<CurriculumVitae> res) {
		for(CurriculumVitae cv : res){
			for(Block block : cv.getBlockList()){
				List<SubBlock> subBlocksToRemove = extractBeginAndEndDateFromSubBlocks(block);
				block.getSubBlockList().removeAll(subBlocksToRemove);			}
		}
	}

	/**
	 * Extract sub blocks which matches to sub tags
	 * 
	 * @param res
	 */
	@SuppressWarnings("unchecked")
	private void extractSubTags(List<CurriculumVitae> res) {
		for(CurriculumVitae cv : res){
			for(Block block : cv.getBlockList()){
				Tag associatedTag = (Tag)block.getAssociatedTag();
				String[] subLines = block.getStringContent().split(IConstantsUtil.LINE_SEPARATOR);
				Bag bag = transformer.extractSubBlocksFromLinesMatching( subLines, associatedTag.getSubTags() );
				block.setSubBlockList((List<SubBlock>)bag.getContentBag().get(0));
				block.setContent((StringBuffer)bag.getContentBag().get(1));
			}
		}
	}

	/**
	 * Calculate size of blocks and decide if they are too long or not 
	 * 
	 * @param res
	 */
	private void calculateOversize(List<CurriculumVitae> res) {
		for(CurriculumVitae cv : res){
			float globalSize = BlockSizerTool.calculateTotalSize(cv.getContent());
			for(Block block : cv.getBlockList()){
				float size = BlockSizerTool.calculateSize(block.getStringContent());
				block.setOverSized(BlockSizerTool.isOverSized(size,globalSize,Float.parseFloat(block.getAssociatedTag().getSizeObjective())));
			}
		}
	}

	/**
	 * Extract blocks matching with tags
	 * 
	 * @param res
	 * @param tags
	 */
	private void extractBlocks(List<CurriculumVitae> res, final List<Tag> tags) {
		for(CurriculumVitae cv : res){
			List<AbstractTag> atags = AbstractTag.toAbstractTagList(tags);
			cv.setContent(cv.getContent().replaceAll("\t\r\n", " "));
			List<AbstractBlock> blocks = transformer.extractBlocksFromLinesMatching(cv.getContent().split(IConstantsUtil.LINE_SEPARATOR), atags);
			cv.setBlockList(AbstractBlock.toBlockList(blocks));
		}
	}

	/**
	 * Extraction of oversized blocks
	 * 
	 * @param res
	 * @param tags
	 */
	private void reAssociationOfOversizedBlocks(List<CurriculumVitae> res, List<Tag> tags) {
		for(CurriculumVitae cv : res){
			List<AbstractTag> atags = TagTool.clone(AbstractTag.toAbstractTagList(tags));
			// remove identite
			TagTool.removeTag(atags, "identite");
			List<AbstractBlock> overSizedBlocks = transformer.extractOverSizedBlocks(cv.getBlockList());
			for (AbstractBlock aBlock : overSizedBlocks){
				reAssociateOversizedBlock(cv, atags, aBlock);
			}
		}
	}

	/**
	 * Reassociation of a block
	 * 
	 * @param cv
	 * @param atags
	 * @param aBlock
	 */
	private void reAssociateOversizedBlock(CurriculumVitae cv, List<AbstractTag> atags,
			AbstractBlock aBlock) {
		AbstractTag currentTag = null;
		List<AbstractBlock> blocks = new ArrayList<AbstractBlock>();
		AbstractBlock currentBlock = null;
		List<AbstractTag> tagList = TagTool.clone(atags);
		for(String line : aBlock.getStringContent().split(IConstantsUtil.LINE_SEPARATOR)){
			if ("Baccalaureat en informatique, Genie logiciel".equalsIgnoreCase(line)){
				int a = 0;
				a++;
			}
			AbstractTag taggingMatch = parsingEngineService.getFirstFoundTag(line, tagList);
			if(taggingMatch != null){
				tagList.remove(taggingMatch);
			}
			switch (calculateWhatToDo(currentTag,taggingMatch)) {
				case CREATE_BLOCK:
					currentBlock = createBlock(aBlock, currentBlock, line);
				break;
				case MOVE_TO_NEXT_TAG:
					currentBlock = moveNextTag(blocks, currentBlock,line, taggingMatch);
					currentTag = taggingMatch;
				break;
				case KEEP_FILLING_BLOCK:
					currentBlock.getContent().append(IConstantsUtil.LINE_SEPARATOR).append(line);
				break;
				case NOT_HANDLED:
					throw new IllegalStateException("Not handled decision. Internal error");
			}
		}
		if(currentBlock != null){
			blocks.add(currentBlock);
		}
		refillBlockWithReassociatedContent(cv, atags, aBlock, blocks);
	}

	/**
	 * Erase content of the block and add the pertinent extracted content (juste erased) in the block which has been chosen (could be the same)
	 * 
	 * @param cv
	 * @param atags
	 * @param aBlock
	 * @param blocks
	 */
	private void refillBlockWithReassociatedContent(CurriculumVitae cv, List<AbstractTag> atags,AbstractBlock aBlock, List<AbstractBlock> blocks) {
		aBlock.setContent(new StringBuffer());
		for(AbstractBlock extractedBlock : blocks){
			AbstractBlock cvBlock = BlockTool.findBlock(cv.getBlockList(),extractedBlock.getAssociatedTag().getLabel());
			if (cvBlock != null){
				cvBlock.getContent().append(extractedBlock.getContent());	
			}
			atags.remove(extractedBlock.getAssociatedTag());
		}
	}
	
	/**
	 * Decide what to do depending on params
	 * 
	 * @param taggingMatch
	 * @param currentTag
	 * @return
	 */
	private WhatToDo calculateWhatToDo(final AbstractTag currentTag, final AbstractTag taggingMatch) {
		WhatToDo res = WhatToDo.NOT_HANDLED;
		if (currentTag == null){
			if (taggingMatch == null){
				res = WhatToDo.CREATE_BLOCK;
			} else {
				res = WhatToDo.MOVE_TO_NEXT_TAG;
			}
		} else {
			if (taggingMatch == null){
				res = WhatToDo.KEEP_FILLING_BLOCK;
			} else {
				res = WhatToDo.MOVE_TO_NEXT_TAG;
			}
		}
		return res;
	}

	/**
	 * add the previous block and create a new one
	 * 
	 * @param blocks
	 * @param currentBlock
	 * @param line
	 * @param taggingMatch
	 * @return
	 */
	private AbstractBlock moveNextTag(List<AbstractBlock> blocks,AbstractBlock currentBlock, String line, AbstractTag taggingMatch) {
		if (currentBlock != null ){
			blocks.add(currentBlock);
		}
		AbstractBlock block = AbstractBlock.getNewInstance();
		block.setAssociatedTag(taggingMatch);
		block.getContent().append(IConstantsUtil.LINE_SEPARATOR).append(line);
		return block;
	}

	/**
	 * create a new block if the current is null fill the current otherwise
	 * 
	 * @param aBlock
	 * @param currentBlock
	 * @param line
	 * @return
	 */
	private AbstractBlock createBlock(final AbstractBlock aBlock,final AbstractBlock currentBlock,final String line) {
		AbstractBlock block = currentBlock;
		if (block == null){
			block = AbstractBlock.getNewInstance();
			block.setAssociatedTag(aBlock.getAssociatedTag());
		}
		block.getContent().append(IConstantsUtil.LINE_SEPARATOR).append(line);
		return block;
	}

	/**
	 * - Whenever a date block is found try to associate it with the previous block (if not a date block) => beginDate sub-sub-block
	 * - Whenever a date block is found search the next block, if the next block is a date block => endDate sub-sub-block
	 * 
	 * @param block
	 * @return
	 */
	private List<SubBlock> extractBeginAndEndDateFromSubBlocks(Block block) {
		List<SubBlock> subBlocksToRemove = new ArrayList<SubBlock>();
		SubBlock previousSubBlock = null;
		Iterator<SubBlock> subBlockIterator = block.getSubBlockList().iterator();
		if (subBlockIterator != null){
			while ( subBlockIterator.hasNext() ){
				SubBlock currentSubBlock = subBlockIterator.next();
				if (DateTool.isDateSubTag(currentSubBlock.getAssociatedTag().getLabel())){
					if (previousSubBlock != null && !"date".equalsIgnoreCase(previousSubBlock.getAssociatedTag().getLabel())){
						previousSubBlock.setStartDate(currentSubBlock.getStringContent());
						subBlocksToRemove.add(currentSubBlock);
						// on regarde si il y a aussi une date de fin juste apres la date de debut
						previousSubBlock = searchEndDate(subBlocksToRemove,	previousSubBlock, subBlockIterator);
					} else {
						previousSubBlock = currentSubBlock;
					}
				} else {
					previousSubBlock = currentSubBlock;
				}
			}
		}
		return subBlocksToRemove;
	}

	/**
	 * search in the forward block if this is a date block and use it if this is one
	 * 
	 * @param subBlocksToRemove
	 * @param previousSubBlock
	 * @param subBlockIterator
	 * @return
	 */
	private SubBlock searchEndDate(List<SubBlock> subBlocksToRemove,SubBlock previousSubBlock, Iterator<SubBlock> subBlockIterator) {
		if (subBlockIterator.hasNext()){
			SubBlock nextSubBlock = subBlockIterator.next();
			if (DateTool.isDateSubTag(nextSubBlock.getAssociatedTag().getLabel())){
				previousSubBlock.setEndDate(nextSubBlock.getStringContent());
				subBlocksToRemove.add(nextSubBlock);
			}
			previousSubBlock = nextSubBlock;
		}
		return previousSubBlock;
	}
}
